package com.example.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.common.Interface.cart.entity.vo.CartItemVO;
import com.example.common.Interface.product.entity.dto.*;
import com.example.common.Interface.product.entity.vo.ProductVO;
import com.example.common.Interface.product.service.ProductService;
import com.example.common.response.BizException;
import com.example.common.util.lock.RedisLockUtils;
import com.example.product.RocketMq.MQProductProducer;
import com.example.product.converter.ProductConverter;
import com.example.product.dao.mapper.CategoryMapper;
import com.example.product.dao.mapper.ProductMapper;
import com.example.product.dao.mapper.ProductSkuMapper;
import com.example.product.dao.po.Category;
import com.example.product.dao.po.Product;
import com.example.product.dao.po.ProductSku;
import com.example.product.enums.ProductTypeEnum;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@DubboService
@Service
@Slf4j
public class ProductServiceImpl implements ProductService {
    @Resource
    ProductMapper productMapper;
    @Resource
    private CategoryMapper categoryMapper;
    @Resource
    ProductSkuMapper productSkuMapper;

    @Autowired
    private MQProductProducer mqProductProducer;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedisLockUtils redisLock;
    private final Cache<String,Product> productCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(5)).maximumSize(1000).build();

    private static final String PRODUCT_ID_KEY_PREFIX = "product:id:";
    private static final String PRODUCT_PRICE_ID_KEY_PREFIX = "product_price:id:";
    private static final String LOCK_KEY_PREFIX = "lock:";

    private static final String PRODUCT_SKU_KEY_PREFIX = "product:sku";

    private static final String DEDUCT_STOCK_LUA =
            "local stockKey = KEYS[1]\n" +
                    "for i = 1, #ARGV, 2 do\n" +
                    "    local productId = ARGV[i]\n" +
                    "    local quantity = tonumber(ARGV[i+1])\n" +
                    "    local raw = redis.call('HGET', stockKey, productId)\n" +
                    "    local current = tonumber(raw) or 0\n" +
                    "    if current < quantity then\n" +
                    "        return 0\n" +
                    "    end\n" +
                    "end\n" +
                    "for i = 1, #ARGV, 2 do\n" +
                    "    local productId = ARGV[i]\n" +
                    "    local quantity = tonumber(ARGV[i+1])\n" +
                    "    redis.call('HINCRBY', stockKey, productId, -quantity)\n" +
                    "end\n" +
                    "return 1";
    private static final String ADD_STOCK_LUA =
            "local stockKey = KEYS[1]\n" +
                    "for i = 1, #ARGV, 2 do\n" +
                    "    local productId = ARGV[i]\n" +
                    "    local quantity = tonumber(ARGV[i+1])\n" +
                    "    redis.call('HINCRBY', stockKey, productId, quantity)\n" +
                    "end\n" +
                    "return 1";
    private <T> RedisScript<T> redisScript(String script, Class<T> resultType) {
        return new DefaultRedisScript<>(script, resultType);
    }

    public Product getProduct(long productId){
        String key = PRODUCT_ID_KEY_PREFIX + productId;
        Product product = productCache.getIfPresent(key);
        if(product != null){
            return product;
        }
        var productJson = redisTemplate.opsForValue().get(key);
        if(productJson != null){
            product = (Product) productJson;
            productCache.put(key, product);
            return product;
        }
        var value = Math.random();
        boolean getLock = this.redisLock.getLock(LOCK_KEY_PREFIX + key, value);
        if(getLock){
            log.info("{}:成功获取[{}]锁",Thread.currentThread().getName(), key);
            product = productMapper.selectById(productId);
            if(product == null){
                redisLock.releaseLock(key, value);
                log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
                return null;
            }
            redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(5));
            productCache.invalidate(key);
            mqProductProducer.sendOneWayMsg(key);
            redisLock.releaseLock(LOCK_KEY_PREFIX + key,value);
            log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
            return product;
        }
        return null;
    }

    public Long getProductPrice(long productId){
        String key = PRODUCT_PRICE_ID_KEY_PREFIX + productId;
        var price = redisTemplate.opsForValue().get(key);
        if(price != null){
            return (Long) price;
        }
        var value = Math.random();
        boolean getLock = this.redisLock.getLock(LOCK_KEY_PREFIX + key, value);
        if(getLock){
            log.info("{}:成功获取[{}]锁",Thread.currentThread().getName(), key);
            LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ProductSku::getProductId, productId);
            var product = productSkuMapper.selectById(productId);
            if(product == null){
                redisLock.releaseLock(key, value);
                log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
                return null;
            }
            redisTemplate.opsForValue().set(key, product.getPrice(), Duration.ofMinutes(5));
            redisLock.releaseLock(LOCK_KEY_PREFIX + key,value);
            log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
            return product.getPrice();
        }
        return null;
    }

    public boolean updateProduct(Product product){
        int res = productMapper.updateById(product);
        if(res <= 0){
            return false;
        }
        String keyRedisProduct = PRODUCT_ID_KEY_PREFIX + product.getId();
        String keyRedisPrice = PRODUCT_PRICE_ID_KEY_PREFIX + product.getId();
        redisTemplate.delete(keyRedisProduct);
        redisTemplate.delete(keyRedisPrice);
        return true;
    }

    public void deleteProductCache(String key){
        productCache.invalidate(key);
    }

    @Override
    public InsertProductRespDTO insert(InsertProductReqDTO request) {
        log.info("insert rpc 服务被调用");
        var product = request.getProduct();
        var userId = request.getUserId();
        if(! isValidityUserId(userId) || ! isValidityProduct(product)){
            return InsertProductRespDTO.builder()
                    .result(false)
                    .total_category(0)
                    .inserted_category(0)
                    .productId(-1L)
                    .build();
        }
        var newProduct = ProductConverter.toProduct(product);
        newProduct.setCreater(userId);
        newProduct.setUpdater(userId);

        int res = productMapper.insert(newProduct);

        if(res <= 0){
            log.warn("商品插入失败");
            return InsertProductRespDTO.builder()
                    .result(false)
                    .total_category(product.getCategories().size())
                    .inserted_category(0)
                    .productId(-1L)
                    .build();
        }

        var categories = product.getCategories().stream()
                .map(ProductTypeEnum::getTypeCodeByTypeMsg).toList();

        var resp = InsertProductRespDTO.builder()
                .result(true)
                .total_category(product.getCategories().size())
                .inserted_category(0)
                .productId(newProduct.getId())
                .build();

        for(var category : categories){
            if(category == -1){
                log.warn("商品分类未定义" + category);
            }else{
                res = categoryMapper.insert(Category.builder()
                        .productId(newProduct.getId())
                        .categoryCode(category)
                        .build());
                if(res <= 0){
                    log.warn("商品分类插入失败" + category);
                }else{
                    resp.setInserted_category(resp.getInserted_category() + 1);
                }
            }
        }
        return resp;
    }

    @Override
    public List<InsertProductRespDTO> insertList(List<InsertProductReqDTO> request) {
        log.info("insertList rpc 服务被调用");
        var resp = new ArrayList<InsertProductRespDTO>();
        for(var req : request){
            if(req == null){
                log.warn("商品为空");
                resp.add(null);
            }else{
                resp.add(insert(req));
            }
        }
        return resp;
    }

    @Override
    public ListProductsRespDTO list(ListProductsReqDTO request) {
        log.info("list rpc 服务被调用");
        Integer page = request.getPage();
        Long pageSize = request.getPageSize();
        String categoryName = request.getCategoryName();
        if(page == null || pageSize == null || page <= 0 || pageSize <= 0){
            log.warn("页错误");
            throw new BizException("页错误");
        }
        Integer typeCode = ProductTypeEnum.fromTypeMsg(categoryName).getTypeCode();
        if(typeCode == null || typeCode == -1){
            log.warn("分类错误");
            throw new BizException("分类错误");
        }
        Page<Long> productPage = productMapper.selectPaginatedIdsByCategory(new Page<>(page, pageSize), typeCode);
        List<ProductVO> resp = new ArrayList<>();
        for(var productId : productPage.getRecords()){
            var product = getProduct(productId);
            if(product == null){
                log.warn("商品不存在" + productId);
            }else{
                resp.add(ProductConverter.toProductVO(product, getProductPrice(productId)));
            }
        }
        return ListProductsRespDTO.builder()
               .products(resp)
               .build();
    }

    @Override
    public DeleteProductRespDTO delete(DeleteProductReqDTO request){
        log.info("delete rpc 服务被调用");
        var productId = request.getProductId();
        if(! isValidityProductId(productId)){
            log.warn("商品id错误");
            throw new BizException("商品ID错误");
        }
        int num = productMapper.deleteById(productId);
        if(num == 1){
            return DeleteProductRespDTO.builder()
                    .result(true)
                    .build();
        }
        return DeleteProductRespDTO.builder()
                .result(false)
                .build();
    }

    @Override
    public SearchProductsRespDTO search(SearchProductsReqDTO request){
        // todo
        return null;
    }

    @Override
    public UpdateProductRespDTO update(UpdateProductReqDTO request){
        var productId = request.getProductId();
        var newProduct = request.getNewProduct();
        if(! isValidityProductId(productId) ||! isValidityProduct(newProduct)){
            log.warn("商品id错误");
            throw new BizException("商品ID错误");
        }
        return UpdateProductRespDTO.builder()
                .result(updateProduct(ProductConverter.toProduct(newProduct)))
                .build();
    }

    @Override
    public GetProductRespDTO get(GetProductReqDTO request) {
        log.info("get rpc 服务被调用");
        var productId = request.getId();
        if(! isValidityProductId(productId)){
            log.warn("商品id错误");
            throw new BizException("商品ID错误");
        }
        var product = getProduct(productId);
        return GetProductRespDTO.builder()
                .product(ProductConverter.toProductVO(product, getProductPrice(productId)))
                .build();
    }

    @Override
    public List<GetProductRespDTO> getList(List<GetProductReqDTO> request){
        log.info("getList rpc 服务被调用");
        var resp = new ArrayList<GetProductRespDTO>();
        for(var req : request){
            if(req == null){
                log.warn("商品为空");
                resp.add(null);
            }else{
                resp.add(get(req));
            }
        }
        return resp;
    }

    @Override
    public boolean deductStock(List<CartItemVO> products) {
        log.info("deductStock rpc 服务被调用");

        List<String> args = products.stream()
                .filter(item -> item != null
                        && isValidityProductId(item.getProductId())
                        && item.getQuantity() > 0)
                .flatMap(item -> Stream.of(
                        item.getProductId().toString(),
                        String.valueOf(item.getQuantity())
                ))
                .collect(Collectors.toList());
        if(args.isEmpty()){
            return true;
        }
        return true;
//        Long result = redisTemplate.execute(
//                redisScript(DEDUCT_STOCK_LUA, Long.class),
//                List.of(PRODUCT_SKU_KEY_PREFIX),
//                args.toArray()
//        );

//        return result != null && result == 1;
    }
    @Override
    public boolean addStock(List<CartItemVO> products) {
        log.info("addStock rpc 服务被调用");
        List<String> args = products.stream()
                .filter(item -> item != null
                        && isValidityProductId(item.getProductId())
                        && item.getQuantity() > 0)
                .flatMap(item -> Stream.of(
                        item.getProductId().toString(),
                        String.valueOf(item.getQuantity())
                ))
                .collect(Collectors.toList());

        if (args.isEmpty()) {
            return true;
        }
        Long result = redisTemplate.execute(
                redisScript(ADD_STOCK_LUA, Long.class),
                List.of(PRODUCT_SKU_KEY_PREFIX),
                args.toArray()
        );
        return result != null && result == 1;
    }


    public boolean isValidityProduct(ProductVO product) {
        String name = product.getName();
        if(name == null || ! isValidityName(name)){
            return false;
        }

        String description = product.getDescription();
        if(description == null || ! isValidityName(description)){
            return false;
        }

        var price = product.getPrice();
        if(price == null || price <= 0){
            return false;
        }

        List<Integer> categories = product.getCategories().stream()
                .map(ProductTypeEnum::getTypeCodeByTypeMsg).toList();
        for (var category : categories){
            if(category == -1){
                return false;
            }
        }
        return true;
    }

    public boolean isValidityName(String name){
        //todo 接入ai
        return true;
    }

    public boolean isValidityDescription(String description){
        //todo
        return true;
    }

    public boolean isValidityUserId(Long userId){
        if(userId == null || userId <= 0){
            log.warn("用户ID错误");
            return false;
        }
        //todo
        return true;
    }

    public boolean isValidityProductId(Long productId){
        if(productId == null || productId <= 0){
            log.warn("商品ID错误");
            return false;
        }
        //todo
        return true;
    }
}
